public class FixRowLayoutManager extends GridLayoutManager {
    /**
     * 行数，列数
     *
     * 如果行数不指定，则为 -1，以为不限制行数
     * 如果列数不指定，则为 1，效果类似于 LinearLayoutManager
     * */
    private int rowCount = -1;
    private int colCount = 1;
    /**RecyclerView的Item回收器*/
    private RecyclerView.Recycler recycler;

    /**RecyclerView的状态器*/
    private RecyclerView.State state;
    /**
     * RV 目前的高度
     * */
    private int curRvHeight = 0;
    /**
     * 垂直方向上偏移的 Y 值(滚动量)
     * */
    private int offsetY = 0;
    /**
     * 存储每行高度
     * */
    private Vector<Integer> rowMaxHeight;
    /**
     * 所有 Item 的布局位置的信息
     * */
    private SparseArray<Rect> mAllItemFrames = new SparseArray<>();
    /**
     * 动画
     * */
    private ValueAnimator anim;

    //private Context context;

    public FixRowLayoutManager(Context context, int rowCount, int colCount) {
        super(context, colCount);
        //this.context = context;
        this.rowCount = rowCount;
        this.colCount = colCount >= 1? colCount : 1;
        rowMaxHeight = new Vector<>();
    }

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT,
            RecyclerView.LayoutParams.WRAP_CONTENT);
    }

    /**
     * 布局效果：
     * 1、如果指定了 2 行，但实际只有 1 行，则取 1 行的高度
     * 2、如果指定了 5 行，但实际只够 2 行，则取 2 行的高度
     * 3、如果指定了精准的高度，则取精准的高度
     * 4、指定高度下，如果无法完全显示，则可滚动
     *
     * 只在 RV 是 AT_MOST 时才手动处理
     * */
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if(recycler == null || state == null || rowCount <= 0 || getHeightMode() != View.MeasureSpec.AT_MOST) {
            super.onLayoutChildren(recycler, state);
            return;
        }

        this.recycler = recycler;
        this.state = state;

        if (state.getItemCount() <= 0) {
            // 无可用 item，先把所有的都回收了，参考自 LinearLayoutManager
            removeAndRecycleAllViews(recycler);
            offsetY = 0;
            return;
        }

        // 暂时废弃正在显示的 item
        detachAndScrapAttachedViews(recycler);
        // 重新测量时先清除数据
        rowMaxHeight.clear();
        mAllItemFrames.clear();

        // 测量并计算每一行的最大高度
        measureAndGetMaxHeightForPerRow(recycler, state);
        // 每行的 item 均分宽度，并布局 item
        splitAverageAndLayoutChild(recycler, state);
    }

    /**
     * 处理滑动，dy > 0，手指从下到上滑动(列表向下滚动)；dy < 0，手指从上到下滑动(列表向上滚动)
     *
     * 只在 RV 是 AT_MOST 时才手动处理
     * */
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if(recycler == null || state == null|| rowCount <= 0 || getHeightMode() != View.MeasureSpec.AT_MOST) {
            return super.scrollVerticallyBy(dy, recycler, state);
        }
        if (getChildCount() == 0 || dy == 0) {
            return 0;
        }
        cancelAnim();
        // 暂时废弃正在显示的 item
        detachAndScrapAttachedViews(recycler);
        int lastOffset = offsetY;
        // 更新偏移量
        offsetY += dy;
        if(offsetY < 0) {
            offsetY = 0;
        }

        if(offsetY > getMaxOffset(state)) {
            offsetY = getMaxOffset(state);
        }

        layoutItems();
        if(lastOffset == offsetY) {
            // 滑动位置未改变
            return 0;
        } else {
            return dy;
        }
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        int finalOffset = getTotalHeight(0, position);
        startScroll(offsetY, finalOffset);
    }

    /**
     * 重写此方法，用于最终设置尺寸
     *
     * 只在 RV 是 AT_MOST 时才手动处理
     * */
    @Override
    public void setMeasuredDimension(int widthSize, int heightSize) {
        if(rowCount <= 0 || getHeightMode() != View.MeasureSpec.AT_MOST) {
            curRvHeight = heightSize;
            super.setMeasuredDimension(widthSize, heightSize);
            return;
        }

        // 获取特定行数的高度
        curRvHeight = getTotalHeight(0, rowCount);
        // RV 高度取较小值
        if(curRvHeight > heightSize) {
            curRvHeight = heightSize;
        }
        super.setMeasuredDimension(widthSize, curRvHeight);
    }

    /**
     * 水平方向上平分
     * 当 item 的宽度 > 平分宽度时，取平分宽度
     * 当 item 的宽度 < 平分宽度时，取 item 宽度，并平均宽度内居中
     *
     * @return pair.first 代表的是每个 item 的建议宽度
     *         pair.second 代表的每个 item 向右偏移的额外宽度
     * */
    private int getItemFinalWidth(View view) {
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams();
        return getDecoratedMeasuredWidth(view) + params.leftMargin + params.rightMargin;
    }

    private int getItemFinalHeight(View view) {
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams();
        return (rowMaxHeight.get(0) + params.topMargin + params.bottomMargin);
    }

    /**
     * 测量并计算每一行的最大高度
     * */
    private void measureAndGetMaxHeightForPerRow(RecyclerView.Recycler recycler, RecyclerView.State state) {
        View item;
        int rowIndex = 0;
        // 测量第 1 次，找到一行中最高的 item
        for(int i = 0; i < state.getItemCount(); i++) {
            // 获取 View
            item = recycler.getViewForPosition(i);
            // 添加 View
            addView(item);
            // 测量 View
            measureChildWithMargins(item, 0, 0);
            // 每行第 1 个
            if(i % colCount == 0) {
                rowIndex = i / colCount;
                rowMaxHeight.add(getDecoratedMeasuredHeight(item));
            } else if (rowMaxHeight.get(rowIndex) < getDecoratedMeasuredHeight(item)) {
                rowMaxHeight.set(rowIndex, getDecoratedMeasuredHeight(item));
            }
        }
    }
    /**
     * 一行内的 item 均分宽度，并且布局 item
     * 下个 item 的左边界，是上个 item 的右边界
     * 下个 item 的上边界，是上个 item 的下边界
     * */
    private void splitAverageAndLayoutChild(RecyclerView.Recycler recycler, RecyclerView.State state) {
        View item;
        // 每个 item 的位置
        int left, top, right = 0, bottom = 0, tempW, width, rowIndex = 0, extraMargin = 0, extraPadding;
        // 平分后，每个 item 的宽度
        int average = (getWidth() - getPaddingStart() - getPaddingEnd()) / colCount;
        // 测量第 2 次，给每个 item 设置高度
        for(int i = 0; i < state.getItemCount(); i++) {
            if(i % colCount == 0) {
                rowIndex = i / colCount;
            }
            item = recycler.getViewForPosition(i);

            tempW = getItemFinalWidth(item);
            width = average > tempW ? tempW : average;

            // 为了保证 item 在平均宽度内能居中，而需要额外移动的量
            extraPadding = (average - width) / 2;

            if(i % colCount == 0) {
                // 每行第 1 个
                // 确定 View 的位置
                left = getPaddingLeft() + extraPadding;
            } else {
                // 下个 item 的左边解，应为上个 item 的右边界 + 额外位移
                left = right + extraMargin;
            }
            right = left + width;
            item.getLayoutParams().width = width;
            item.getLayoutParams().height = rowMaxHeight.get(rowIndex);
            extraMargin = extraPadding;

            top = rowIndex == 0 ? (getPaddingTop() - offsetY)
                : bottom; // 当前行数位于上一行的下方

            bottom = top + getItemFinalHeight(item);

            // 保存信息
            Rect frame = mAllItemFrames.get(i);
            if (frame == null) {
                frame = new Rect();
            }
            frame.set(left, top, right, bottom);
            mAllItemFrames.put(i, frame);

            layoutItems();
        }
    }

    private void layoutItems() {
        if(state == null || state.isPreLayout()) {
            return;
        }
        // 先判断 item 是否在屏幕内，屏幕内的才进行布局
        Rect displayFrame = getRVDisplayRect();
        for (int i = 0; i < state.getItemCount(); i++) {
            View child = recycler.getViewForPosition(i);

            Rect rect = mAllItemFrames.get(i);

            if (Rect.intersects(displayFrame, rect)) {
                //Item还在显示区域内，更新滑动后 Item 的位置
                layoutDecorated(child,
                    rect.left - offsetY,
                    rect.top,
                    rect.right - offsetY,
                    rect.bottom);
            } else {
                //Item 没有在显示区域，就说明需要回收
                removeAndRecycleView(child, recycler);
            }


        }
    }

    /**
     * 获取最大的可偏移量
     *
     * 鉴于布局只手动处理 AT_MOST 的情况，所以此处也只有：
     * AT_MOST 时 item 已显示完和未显示完 2 种情况
     * */
    private int getMaxOffset(RecyclerView.State state) {
        if(state.getItemCount() == 0) {
            return 0;
        }
        if(state.getItemCount() <= rowCount * colCount) {
            // item 可显示完，不需要滚动
            return 0;
        } else  {
            // item 显示不完，需要滚动(滚动距离为总行数的高度 - RV 的高度)
            return getTotalHeight(0, -1) - curRvHeight;
        }
    }

    /**
     * 参数值无法改变，而且手动判空还报错，得临时定义中间变量，简直是醉了
     *
     * 获取指定行数的高度，必须在测量后进行。如果想要获取所有行的高度，可以传入负数
     *
     * @param start 开始的行号
     * @param end   结束的行号
     * */
    private int getTotalHeight(int start, int end) {

        if(end < 0) {
            end = rowMaxHeight.size();
        }
        if(start < 0) {
            start = 0;
        }

        if(end <= 0) {
            return 0;
        }

        int h = 0;
        for(int i = start; i < end; i++) {
            h += rowMaxHeight.get(i);
        }
        return h;
    }
    /**
     * 获取 RV 可显示内容的区域
     * */
    private Rect getRVDisplayRect() {
        return new Rect(
            getPaddingLeft(),
            getPaddingTop(),
            getWidth() - getPaddingRight(),
            getHeight() - getPaddingBottom()
        );
    }

    /**
     * 滚动到指定X轴位置
     * @param from X轴方向起始点的偏移量
     * @param to X轴方向终点的偏移量
     */
    private void startScroll(int from, int to) {
        if (anim != null && anim.isRunning()) {
            anim.cancel();
        }
        anim = ValueAnimator.ofFloat(from, to);
        anim.setDuration(500);
        anim.setInterpolator(new DecelerateInterpolator());
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                offsetY = (int)animation.getAnimatedValue();
                layoutItems();
            }
        });
        anim.start();
    }

    private void cancelAnim() {
        if(anim == null || anim.isRunning()) {
            return;
        }
        anim.cancel();
    }
}